#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/of_platform.h>
#include "demo.h"

struct demo_device *pdev = NULL;

static int demo_open(struct inode *inode, struct file *fd)
{
    demo_info("-----%s-----\n", __FUNCTION__);
    struct demo_device *pdev = NULL;
    struct miscdevice *pmiscdev = NULL;
    if (!fd || !fd->private_data) {
        demo_err("invalid param fd");
        return -ENODEV;
    }
    pmiscdev = (struct miscdevice *)fd->private_data;
    pdev = container_of(pmiscdev, struct demo_device, device);
    if (!pdev) {
        demo_err("invalid param pdev");
        return -EINVAL;
    }
    // todo
    fd->private_data = (void *)pdev;
    return 0;
}

static int demo_release(struct inode *inode, struct file *fd)
{
    demo_info("-----%s-----\n", __FUNCTION__);
    struct demo_device *pdev = NULL;
    if (!fd || !fd->private_data) {
        demo_err("invalid param fd");
        return -ENODEV;
    }
    pdev = fd->private_data;
    // tode
    return 0;
}

static long ioctl_demo_io(struct file *fd, unsigned long args)
{
    struct demo_device *pdev = NULL;
    if (!fd || !fd->private_data) {
        demo_err("invalid param fd");
        return -ENODEV;
    }
    demo_info("--%s--\n", __FUNCTION__);
    pdev = fd->private_data;
    demo_info("%s", pdev->name);
    return 0;
}

static long ioctl_demo_ior(struct file *fd, unsigned long args)
{
    int ret;
    struct demo_msg msg = {
        .name = "chrdev",
        .value = 4,
        .data = "chrdev test",
    };
    struct demo_device *pdev = NULL;
    if (!fd || !fd->private_data || !args) {
        demo_err("invalid param fd");
        return -ENODEV;
    }
    demo_info("--%s--\n", __FUNCTION__);
    pdev = fd->private_data;
    ret = copy_to_user((void __user *)args, (void *)&msg, sizeof(struct demo_msg));
    if (ret) {
        demo_err("copy_to_user failed\n");
        return ret;
    }
    return 0;
}

static long ioctl_demo_iow(struct file *fd, unsigned long args)
{
    int ret;
    struct demo_msg msg;
    struct demo_device *pdev = NULL;
    if (!fd || !fd->private_data || !args) {
        demo_err("invalid param fd");
        return -ENODEV;
    }
    demo_info("--%s--\n", __FUNCTION__);
    pdev = fd->private_data;
    ret = copy_from_user((void *)&msg, (void __user *)args, sizeof(struct demo_msg));
    if (ret) {
        demo_err("copy_from_user failed\n");
        return ret;
    }
    demo_info("name = %s, value = %d\n", msg.name, msg.value);
    return 0;
}

static long ioctl_demo_iowr(struct file *fd, unsigned long args)
{
    int ret;
    struct demo_msg msg;
    struct demo_device *pdev = NULL;
    if (!fd || !fd->private_data || !args) {
        demo_err("invalid param fd");
        return -ENODEV;
    }
    demo_info("--%s--\n", __FUNCTION__);
    pdev = fd->private_data;
    ret = copy_from_user((void *)&msg, (void __user *)args, sizeof(struct demo_msg));
    if (ret) {
        demo_err("copy_from_user failed\n");
        return ret;
    }
    demo_info("name = %s, value = %d\n", msg.name, msg.value);

    strncpy(msg.name, "demochr", strlen("demochr") + 1);
    msg.value = -1;
    ret = copy_to_user((void __user *)args, (void *)&msg, sizeof(struct demo_msg));
    if (ret) {
        demo_err("copy_to_user failed\n");
        return ret;
    }

    return 0;
}

static struct demo_ioctl_ops ioctl_normal_ops[] = {
    DEMO_IOCTL_OPS(DEMO_IOCTL_IO, ioctl_demo_io),
    DEMO_IOCTL_OPS(DEMO_IOCTL_IOR, ioctl_demo_ior),
    DEMO_IOCTL_OPS(DEMO_IOCTL_IOW, ioctl_demo_iow),
    DEMO_IOCTL_OPS(DEMO_IOCTL_IOWR, ioctl_demo_iowr),
};

static demo_ioctl_func_t demo_get_ioctl_func(unsigned int cmd, struct demo_ioctl_ops *ops_tbl, unsigned int tbl_size)
{
    demo_info("--%s--\n", __FUNCTION__);
    unsigned int idx;
    for (idx = 0; idx < tbl_size; idx++) {
        if (cmd == ops_tbl[idx].cmd)
            return ops_tbl[idx].func;
    }
    return NULL;
}

static long demo_ioctl(struct file *fd, unsigned int cmd, unsigned long args)
{
    demo_info("--%s--\n", __FUNCTION__);
    if (!fd) {
        demo_err("invalid input fd");
        return -EINVAL;
    }
    demo_ioctl_func_t func = demo_get_ioctl_func(cmd, ioctl_normal_ops, ARRAY_SIZE(ioctl_normal_ops));
    if (func)
        return func(fd, args);
    return 0;
}

#ifdef CONFIG_COMPAT
static long demo_ioctl32(struct file *fd, unsigned int cmd, unsigned long args)
{
    demo_info("--%s--\n", __FUNCTION__);
    return demo_ioctl(fd, cmd, args);
}
#endif

static const struct file_operations demo_fops = {
    .owner = THIS_MODULE,
    .open = demo_open,
    .unlocked_ioctl = demo_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = demo_ioctl32,
#endif
    .release = demo_release
};

static int misc_device_init(struct miscdevice *misc)
{
    demo_info("--%s--\n", __FUNCTION__);
    misc->minor = MISC_DYNAMIC_MINOR;
    misc->name = DEMO_MODULE_NAME;
    misc->fops = &demo_fops;
    return 0;
}

static int demo_device_data_init(struct demo_device *pdev)
{
    demo_info("--%s--\n", __FUNCTION__);
    // Todo
    return 0;
}

static int plat_dev_init(struct platform_device *dev, struct demo_device *pdev)
{
    demo_info("--%s--\n", __FUNCTION__);
    pdev->dev = dev;
    platform_set_drvdata(dev, pdev);
    return 0;
}

static int demo_device_init(struct platform_device *dev, struct demo_device *pdev)
{
    demo_info("--%s--\n", __FUNCTION__);
    plat_dev_init(dev, pdev);
    misc_device_init(&pdev->device);
    demo_device_data_init(pdev);
    return 0;
}

static int demo_probe(struct platform_device *dev)
{
    demo_info("--%s--\n", __FUNCTION__);
    pdev = (struct demo_device *)kzalloc(sizeof(struct demo_device), GFP_KERNEL);
    if (!pdev) {
        demo_err("demo device kzalloc failed\n");
        return -ENOMEM;
    }
    pdev->name = "Hello World";
    demo_device_init(dev, pdev);
    return misc_register(&pdev->device);
}

static int demo_remove(struct platform_device *dev)
{
    demo_info("--%s--\n", __FUNCTION__);
    if (pdev)
        kfree(pdev);
    return 0;
}

static struct of_device_id demo_match[] = {
    { .compatible = "dev-demo" },
    {},
};

static struct platform_driver demo_driver = {
    .driver.name = DEMO_MODULE_NAME,
    .driver.owner = THIS_MODULE,
    .probe = demo_probe,
    .remove = demo_remove
};

module_platform_driver(demo_driver);
MODULE_DESCRIPTION("platform demo driver");
MODULE_LICENSE("GPL");
